using System;
using System.Buffers;
using System.Runtime.CompilerServices;

namespace Letter.IO.Binary;

public ref struct SpanWriter
{
    public SpanWriter(Span<byte> buf)
    {
        this.index = 0;
        this.buf = buf;
    }

    private int index;
    private Span<byte> buf;
    
    public int Length
    {
        get { return this.buf.Length; }
    }

    public int WritableLength
    {
        get { return this.Length - this.index; }
    }

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteBoolean(bool value) => this.Mark(sizeof(bool)).FWriteBoolean(value);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteInt8(sbyte value) => this.Mark(sizeof(sbyte)).FWriteInt8(value);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteUInt8(byte value) => this.Mark(sizeof(byte)).FWriteUInt8(value);

    
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteChar(char value) => this.Mark(sizeof(char)).FWriteChar(value);
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteCharLE(char value) => this.Mark(sizeof(char)).FWriteCharLE(value);
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteCharBE(char value) => this.Mark(sizeof(char)).FWriteCharBE(value);
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteString(string value)
    {
        var span = value.AsSpan();
        var length = span.Length;
        this.WriteInt32BE(length);
        foreach (var item in span)
        {
            this.WriteChar(item);
        }
    }


    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteInt16LE(short value) => this.Mark(sizeof(short)).FWriteInt16LE(value);
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteInt16BE(short value) => this.Mark(sizeof(short)).FWriteInt16BE(value);
    

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteUInt16LE(ushort value) => this.Mark(sizeof(ushort)).FWriteUInt16LE(value);
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteUInt16BE(ushort value) => this.Mark(sizeof(ushort)).FWriteUInt16BE(value);
    

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteInt32LE(int value) => this.Mark(sizeof(int)).FWriteInt32LE(value);
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteInt32BE(int value) => this.Mark(sizeof(int)).FWriteInt32BE(value);
    

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteUInt32LE(uint value) => this.Mark(sizeof(uint)).FWriteUInt32LE(value);
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteUInt32BE(uint value) => this.Mark(sizeof(uint)).FWriteUInt32BE(value);
    

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteInt64LE(long value) => this.Mark(sizeof(long)).FWriteInt64LE(value);
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteInt64BE(long value) => this.Mark(sizeof(long)).FWriteInt64BE(value);
    

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteUInt64LE(ulong value) => this.Mark(sizeof(ulong)).FWriteUInt64LE(value);
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteUInt64BE(ulong value) => this.Mark(sizeof(ulong)).FWriteUInt64BE(value);
    

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteFloat32LE(float value) => this.Mark(sizeof(float)).FWriteFloat32LE(value);
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteFloat32BE(float value) => this.Mark(sizeof(float)).FWriteFloat32BE(value);
    

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteFloat64LE(double value) => this.Mark(sizeof(double)).FWriteFloat64LE(value);
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteFloat64BE(double value) => this.Mark(sizeof(double)).FWriteFloat64BE(value);

    
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteBytes(byte[] bytes) => this.InternalWriteSpan(bytes);
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteBytes(byte[] bytes, int offset, int count) => this.InternalWriteSpan(new Span<byte>(bytes, offset, count));

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteBytes(in ArraySegment<byte> segment) => this.InternalWriteSpan(segment.AsSpan());
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteMemory(in Memory<byte> memory) => this.InternalWriteSpan(memory.Span);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteMemory(in ReadOnlyMemory<byte> memory) => this.InternalWriteSpan(memory.Span);

    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteSpan(in ReadOnlySpan<byte> span) => this.InternalWriteSpan(span);

    
    [MethodImpl(MethodImplOptions.AggressiveInlining)]
    public void WriteSequence(ref ReadOnlySequence<byte> sequence)
    {
        if (sequence.IsEmpty)
        {
            throw new ArgumentNullException(nameof(sequence));
        }

        if (sequence.IsSingleSegment)
        {
            this.InternalWriteSpan(sequence.First.Span);
        }
        else
        {
            foreach (var memory in sequence)
            {
                this.InternalWriteSpan(memory.Span);
            }
        }
    }
    
    private void InternalWriteSpan(in ReadOnlySpan<byte> span)
    {
        var buf = this.Mark(span.Length);
        span.CopyTo(buf);
    }
    
    public Span<byte> Mark(int size)
    {
        var memory = this.buf.Slice(index, size);
        this.index += size;
        
        return memory;
    }
}